package top.chukongxiang.monitorservice.service;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import top.chukongxiang.monitorservice.components.PreviewCallbackHandler;
import top.chukongxiang.monitorservice.components.PreviewDataHandler;
import top.chukongxiang.monitorservice.config.SdkServerContextHolder;
import top.chukongxiang.monitorservice.jna.HCNetSDK;
import top.chukongxiang.monitorservice.mapper.ServerConfigMapper;
import top.chukongxiang.monitorservice.model.constant.ValidationGroups;
import top.chukongxiang.monitorservice.model.dto.*;
import top.chukongxiang.monitorservice.model.entity.ServerConfig;
import top.chukongxiang.monitorservice.model.enums.SdkLoginMode;
import top.chukongxiang.monitorservice.model.exception.FxException;
import top.chukongxiang.monitorservice.tools.H2Util;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author 楚孔响
 * @version 1.0
 * @date 2023/10/25 09:36
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class HcNetSdkService {

    private static final int ISAPI_DATA_LEN = 1024 * 1024;
    private static final int ISAPI_STATUS_LEN = 4 * 4096;

    private final HCNetSDK hcNetSDK;

    /**
     * NET_DVR_SetDVRMessageCallBack_V50 注册的回调函数（每个SDK实例最多注册15个）
     */
    private static final List<HCNetSDK.FMSGCallBack_V31> v50SetDvrMsgCallbackCache = new ArrayList<>();

    /**
     * 设备登录V40 与V30功能一致
     */
    public SdkLoginInfoDto loginV40(@Validated(ValidationGroups.SdkLogin.class) ServerConfig serverConfig, boolean cache) {
        String ip = serverConfig.getIp();
        Integer port = serverConfig.getPort();
        String user = serverConfig.getUsername();
        String psw = serverConfig.getPassword();
        SdkLoginMode loginMode = serverConfig.getLoginMode();
        //注册
        //设备登录信息
        HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();
        //设备信息
        HCNetSDK.NET_DVR_DEVICEINFO_V40 deviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();

        loginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
        System.arraycopy(ip.getBytes(), 0, loginInfo.sDeviceAddress, 0, ip.length());

        loginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
        System.arraycopy(user.getBytes(), 0, loginInfo.sUserName, 0, user.length());

        loginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
        System.arraycopy(psw.getBytes(), 0, loginInfo.sPassword, 0, psw.length());

        loginInfo.wPort = port.shortValue();
        // 是否异步登录
        loginInfo.bUseAsynLogin = false;
        // 0- SDK私有协议，1- ISAPI协议
        loginInfo.byLoginMode = loginMode.getValue().byteValue();
        loginInfo.write();

        int lUserId = hcNetSDK.NET_DVR_Login_V40(loginInfo, deviceInfo);
        if (lUserId == -1) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("登录失败，错误码为：{}", errCode);
            throw new FxException("登录失败，错误码为：" + errCode);
        } else {
            if (cache) {
                SdkServerContextHolder.set(serverConfig.getId(), lUserId, deviceInfo);
                log.info("{} 登录成功，设备类型：{}", ip, SdkServerContextHolder.getDvrType(serverConfig.getId()));
            }
            SdkLoginInfoDto sdkLoginInfoDto = new SdkLoginInfoDto();
            sdkLoginInfoDto.setLoginInfo(loginInfo);
            sdkLoginInfoDto.setDeviceInfo(deviceInfo);
            sdkLoginInfoDto.setLUserId(lUserId);

            return sdkLoginInfoDto;
        }
    }

    public HCNetSDK.NET_DVR_DEVICECFG_V40 getDiskConfig(Long serverId) {
        autoLogin(serverId);
        int lUserId = SdkServerContextHolder.getUserId(serverId);
//        HCNetSDK.NET_DVR_DISKABILITY_LIST list = new HCNetSDK.NET_DVR_DISKABILITY_LIST();
//        boolean success = hcNetSDK.NET_DVR_GetDiskList(lUserId, list);
//        HCNetSDK.NET_DVR_MOUNT_DISK_PARAM param = new HCNetSDK.NET_DVR_MOUNT_DISK_PARAM();
//        boolean success = hcNetSDK.NET_DVR_RemoteControl(lUserId, 6015, param.getPointer(), param.size());


        HCNetSDK.NET_DVR_HDCFG hdcfg = new HCNetSDK.NET_DVR_HDCFG();
        IntByReference intByReference = new IntByReference(0);
        int startChannelNo = SdkServerContextHolder.getStartDChannelNo(serverId);
        boolean success = hcNetSDK.NET_DVR_GetDVRConfig(lUserId, HCNetSDK.NET_DVR_GET_HDCFG_V40, startChannelNo, hdcfg.getPointer(), hdcfg.size(), intByReference);
        if (!success) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("获取硬盘配置失败，错误码为：{}", errCode);
            throw new FxException("获取硬盘配置失败，错误码为：" + errCode);
        }
        return null;
    }

    public HCNetSDK.NET_DVR_IPPARACFG_V40 getIpParamsConfig(Long serverId) {
        autoLogin(serverId);
        int lUserId = SdkServerContextHolder.getUserId(serverId);
        int startChannelNo = SdkServerContextHolder.getStartDChannelNo(serverId);
        HCNetSDK.NET_DVR_IPPARACFG_V40 ipparacfg = new HCNetSDK.NET_DVR_IPPARACFG_V40();
        IntByReference receiveLenReference = new IntByReference(0);
        boolean success = hcNetSDK.NET_DVR_GetDVRConfig(lUserId, HCNetSDK.NET_DVR_GET_IPPARACFG_V40, startChannelNo, ipparacfg.getPointer(), ipparacfg.size(), receiveLenReference);
        if (!success) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("获取 IP 接入配置参数失败，错误码为：{}", errCode);
            throw new FxException("获取 IP 接入配置参数失败，错误码为：" + errCode);
        }
        return ipparacfg;
    }

    /**
     * 远程格式化硬盘
     * @param serverId 服务器id
     * @param diskNo 硬盘编号
     * @return 格式化操作句柄id
     */
    public void formatDisk(Long serverId, int diskNo) {
        formatDisk(serverId, diskNo, null);
    }
    public void formatDisk(Long serverId, int diskNo, StreamProgress callback) {
        autoLogin(serverId);
        int lUserId = SdkServerContextHolder.getUserId(serverId);
        int formatId = hcNetSDK.NET_DVR_FormatDisk(lUserId, diskNo);
        if (formatId == -1) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("硬盘{} 格式化失败，错误码为：{}", diskNo, errCode);
            throw new FxException("硬盘" + diskNo + " 格式化失败，错误码为：" + errCode);
        }
        try {
            IntByReference progress = new IntByReference(0);
            IntByReference formatStatus = new IntByReference(0);
            IntByReference diskNoReference = new IntByReference(diskNo);
            if (callback != null) {
                callback.start();
            }
            while (formatStatus.getValue() == 0 && hcNetSDK.NET_DVR_GetFormatProgress(formatId, diskNoReference, progress, formatStatus)) {
                int progressValue = progress.getValue();
                log.info("格式化硬盘{}，进度：{}", diskNo, progressValue);
                if (callback != null) {
                    callback.progress(100, progressValue);
                }
            }
            if (callback != null) {
                callback.finish();
            }
            if (formatStatus.getValue() != 1) {
                throw new FxException("硬盘" + diskNo + "格式化失败,状态码：" + formatStatus.getValue());
            }
            log.info("格式化硬盘{}成功", diskNo);
        } catch (Exception e) {
            if (e instanceof FxException) {
                throw e;
            }
            throw new FxException(e.getMessage());
        } finally {
            // 关闭
            hcNetSDK.NET_DVR_CloseFormatHandle(formatId);
        }
    }

    public void play(Long serverId) {
        if (SdkServerContextHolder.isPlaying(serverId)) {
            // 正在播放，不能再播放了
            return;
        }
        autoLogin(serverId);
        Integer dvrType = SdkServerContextHolder.getDvrType(serverId);
        if (Objects.equals(202, dvrType)) {
            log.warn("{} 设备类型不支持播放：{}", serverId, dvrType);
            return;
        }
        Integer lUserId = SdkServerContextHolder.getUserId(serverId);
        Byte startChannelNo = SdkServerContextHolder.getStartChannelNo(serverId);
        if (startChannelNo == null) {
            throw new FxException("无法找到通道!{}", serverId);
        }
        HCNetSDK.NET_DVR_PREVIEWINFO previewinfo = new HCNetSDK.NET_DVR_PREVIEWINFO();

        previewinfo.read();

        previewinfo.hPlayWnd = null;

        // 通道号
        previewinfo.lChannel = startChannelNo;

        // 0-主码流，1-子码流，2-码流 3，3-码流 4，以此类推
        previewinfo.dwStreamType = 0;

        // 0- TCP 方式，1- UDP 方式，2- 多播方式，3- RTP 方式，4-RTP/RTSP，5-RSTP/HTT
        previewinfo.dwLinkMode = 0;

        // 0- 非阻塞取流，1- 阻塞取流
        previewinfo.bBlocked = 1;

        previewinfo.write();

        PreviewDataHandler previewDataHandler = new PreviewDataHandler(serverId, (int) startChannelNo);

        HCNetSDK.FRealDataCallBack_V30 callBack = new PreviewCallbackHandler(serverId, previewDataHandler);
        int i = hcNetSDK.NET_DVR_RealPlay_V40(lUserId, previewinfo, callBack, Pointer.NULL);
        if (i == -1) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("预览失败，错误码为：{}", errCode);
            throw new FxException("预览失败，错误码为：" + errCode);
        } else {
            log.info("开始取流：{}", serverId);
        }
    }

    public boolean stop(Long serverId) {
        Integer handleId = SdkServerContextHolder.getPlayHandle(serverId);
        if (handleId == null) {
            log.warn("{} 没有预览流，不能停止播放", serverId);
            return false;
        }
        boolean success = hcNetSDK.NET_DVR_StopRealPlay(handleId);
        if (success) {
            log.info("{} 停止预览成功，句柄id：{}", serverId, handleId);
            SdkServerContextHolder.setPlay(serverId, null);
            return true;
        } else {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("{} 停止预览失败，错误码：{}", serverId, errCode);
            return false;
        }
    }

    public String sendWithRecvRemoteConfig(int handleId, Structure inBuffer) {


        HCNetSDK.BYTE_ARRAY outBuffer = new HCNetSDK.BYTE_ARRAY(1024 * 2);

        IntByReference receiveCountRef = new IntByReference();

        int dwState = hcNetSDK.NET_DVR_SendWithRecvRemoteConfig(handleId, inBuffer.getPointer(), inBuffer.size(),
                outBuffer.getPointer(), outBuffer.size(), receiveCountRef);
        switch (dwState) {
            case -1:
            case HCNetSDK.NET_SDK_CONFIG_STATUS_FAILED:
            case HCNetSDK.NET_SDK_CONFIG_STATUS_EXCEPTION: {
                int errCode = hcNetSDK.NET_DVR_GetLastError();
                log.error("下发远程配置失败,错误码 {}", errCode);
                throw new FxException("下发远程配置失败,错误码 {}", errCode);
            }
            case HCNetSDK.NET_SDK_CONFIG_STATUS_NEED_WAIT: {
                return sendWithRecvRemoteConfig(handleId, inBuffer);
            }
            default: {
                break;
            }
        }
        outBuffer.read();
        return new String(outBuffer.byValue);
    }

    /**
     * 注册全局事件接收函数
     * @param msgCallback
     */
    public void registerSedDvrMessageCallback(HCNetSDK.FMSGCallBack_V31 msgCallback) {
        if (v50SetDvrMsgCallbackCache.size() >= 15) {
            throw new FxException("回调函数最多注册15个");
        }
        v50SetDvrMsgCallbackCache.add(msgCallback);
        boolean success = hcNetSDK.NET_DVR_SetDVRMessageCallBack_V50(v50SetDvrMsgCallbackCache.size(), msgCallback, null);
        if (!success) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("回调函数注册失败！{}", errCode);
            throw new FxException("回调函数注册失败！{}", errCode);
        }
    }

    /**
     * 报警布防
     */
    public int setupAlarmChain(Long serverId) {
        autoLogin(serverId);
        Integer lUserId = SdkServerContextHolder.getUserId(serverId);
        HCNetSDK.NET_DVR_SETUPALARM_PARAM_V50 setupAlarmParamV50 = new HCNetSDK.NET_DVR_SETUPALARM_PARAM_V50();
        setupAlarmParamV50.read();
        setupAlarmParamV50.dwSize = setupAlarmParamV50.size();
        setupAlarmParamV50.byLevel = 0;
        setupAlarmParamV50.byAlarmInfoType = 1;
        setupAlarmParamV50.byRetAlarmTypeV40 = 1;
        setupAlarmParamV50.byRetDevInfoVersion = 0;
        setupAlarmParamV50.byRetVQDAlarmType = 0;
        // 1- 人脸侦测报警(报警类型：COMM_ALARM_FACE_DETECTION，NET_DVR_FACE_DETECTION)，0- 人脸抓拍报警(报警类型：COMM_UPLOAD_FACESNAP_RESULT，NET_VCA_FACESNAP_RESULT)
        setupAlarmParamV50.byFaceAlarmDetection = 1;
        setupAlarmParamV50.bySupport = (byte) 0x80;
        setupAlarmParamV50.byBrokenNetHttp = 0;
        setupAlarmParamV50.write();
        int handleId = hcNetSDK.NET_DVR_SetupAlarmChan_V50(lUserId, setupAlarmParamV50, null, 0);

        if (handleId == -1) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("{} 布防失败！错误码：{}", serverId, errCode);
            throw new FxException("{} 布防失败！错误码：{}", serverId, errCode);
        }

        return handleId;

    }

    /**
     * 查询明眸门禁用户总数
     * @param serverId
     * @return
     */
    public int queryMingmouDoorUserCount(Long serverId) {
        // 查询用户总数
        IsApiResponseDto responseDto = isApiRequest(serverId, "GET /ISAPI/AccessControl/UserInfo/Count?format=json");
        String jsonBody = responseDto.getBody();
        return JSONUtil.parseObj(jsonBody).getJSONObject("UserInfoCount").getInt("userNumber");
    }

    /**
     * 查询明眸门禁用户信息
     * @param serverId
     * @return
     */
    public List<UserInfo> queryMingmouDoorUsers(Long serverId) {
        // 查询用户总数
        int total = queryMingmouDoorUserCount(serverId);
        // 建立长连接
        int handleId = startRemoteConfig(serverId, HCNetSDK.NET_DVR_JSON_CONFIG, "POST /ISAPI/AccessControl/UserInfo/Search?format=json", null);

        List<UserInfo> userInfos = new ArrayList<>();

        Page<UserInfo> page = new Page<>();
        page.setCurrent(0);
        page.setSize(30);
        page.setTotal(total);
        do {
            page.setCurrent(page.getCurrent() + 1);
            JSONObject params = new UserInfoSearchCond()
                    .setSearchID(DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_MS_PATTERN))
                    .setSearchResultPosition((int) ((page.getCurrent() - 1) * page.getSize()))
                    .setMaxResults((int) page.getSize())
                    .toSdkJson();

            String req = params.toString();
            HCNetSDK.BYTE_ARRAY inBuffer = new HCNetSDK.BYTE_ARRAY(req.length());
            inBuffer.read();
            inBuffer.byValue = req.getBytes();
            inBuffer.write();
            String jsonStr = sendWithRecvRemoteConfig(handleId, inBuffer);
            UserInfoSearchResponse userInfoSearchResponse = JSONUtil.toBean(jsonStr, UserInfoSearchResponse.class);
            userInfos.addAll(userInfoSearchResponse.getUserInfoSearch().getUserInfo());
            page.setRecords(userInfoSearchResponse.getUserInfoSearch().getUserInfo());
        } while (page.hasNext());



        // 结束长连接
        stopRemoteConfig(serverId, handleId);

        return userInfos;
    }

    public void deleteMongmouDoorUser(Long serverId, List<String> employeeNos) {
        // 开启长连接
        int handleId = startRemoteConfig(serverId, HCNetSDK.NET_DVR_JSON_CONFIG, "PUT /ISAPI/AccessControl/UserInfo/SetUp?format=json");

        for (String employeeNo : employeeNos) {
            JSONObject params = new JSONObject();
            JSONObject userInfo = new JSONObject();
            userInfo.set("employeeNo", employeeNo);
            userInfo.set("deleteUser", true);
            params.set("UserInfo", userInfo);
            String req = params.toString();
            // 下发人员信息
            HCNetSDK.BYTE_ARRAY inBuffer = new HCNetSDK.BYTE_ARRAY(req.length());
            inBuffer.read();
            inBuffer.byValue = req.getBytes();
            inBuffer.write();
            String jsonStr = sendWithRecvRemoteConfig(handleId, inBuffer);
            HkIsApiResponse hkIsApiResponse = JSONUtil.toBean(jsonStr, HkIsApiResponse.class);
            if (hkIsApiResponse.getStatusCode() != 1) {
                log.error("删除人员失败：{}\n{}", hkIsApiResponse, userInfo);
                throw new FxException("删除人员失败：{}\n{}", hkIsApiResponse, userInfo);
            }
        }

        // 结束长连接
        stopRemoteConfig(serverId, handleId);
    }

    public void addMongmouDoorUser(Long serverId, List<UserInfo> userInfos) {
        // 开启长连接
        int handleId = startRemoteConfig(serverId, HCNetSDK.NET_DVR_JSON_CONFIG, "POST /ISAPI/AccessControl/UserInfo/Record?format=json");

        for (UserInfo userInfo : userInfos) {
            JSONObject params = new JSONObject();
            params.set("UserInfo", userInfo);
            String req = params.toString();
            // 下发人员信息
            HCNetSDK.BYTE_ARRAY inBuffer = new HCNetSDK.BYTE_ARRAY(req.length());
            inBuffer.read();
            inBuffer.byValue = req.getBytes();
            inBuffer.write();
            String jsonStr = sendWithRecvRemoteConfig(handleId, inBuffer);
            HkIsApiResponse hkIsApiResponse = JSONUtil.toBean(jsonStr, HkIsApiResponse.class);
            if (hkIsApiResponse.getStatusCode() != 1) {
                log.error("添加人员失败：{}\n{}", hkIsApiResponse, userInfo);
                throw new FxException("添加人员失败：{}\n{}", hkIsApiResponse, userInfo);
            }
        }

        // 结束长连接
        stopRemoteConfig(serverId, handleId);
    }

    public void updateMongmouDoorUser(Long serverId, List<UserInfo> userInfos) {
        // 开启长连接
        int handleId = startRemoteConfig(serverId, HCNetSDK.NET_DVR_JSON_CONFIG, "PUT /ISAPI/AccessControl/UserInfo/Modify?format=json");

        for (UserInfo userInfo : userInfos) {
            JSONObject params = new JSONObject();
            params.set("UserInfo", userInfo);
            String req = params.toString();
            // 下发人员信息
            HCNetSDK.BYTE_ARRAY inBuffer = new HCNetSDK.BYTE_ARRAY(req.length());
            inBuffer.read();
            inBuffer.byValue = req.getBytes();
            inBuffer.write();
            String jsonStr = sendWithRecvRemoteConfig(handleId, inBuffer);
            HkIsApiResponse hkIsApiResponse = JSONUtil.toBean(jsonStr, HkIsApiResponse.class);
            if (hkIsApiResponse.getStatusCode() != 1) {
                log.error("更新人员失败：{}\n{}", hkIsApiResponse, userInfo);
                throw new FxException("更新人员失败：{}\n{}", hkIsApiResponse, userInfo);
            }
        }

        // 结束长连接
        stopRemoteConfig(serverId, handleId);
    }

    /**
     * 查询人脸列表
     * @param serverId
     */
    public List<FDRecordDataInfo> queryFaces(Long serverId) {
        IsApiResponseDto response = isApiRequest(serverId, "GET /ISAPI/Intelligent/FDLib/Count?format=json");
        FDRecordDataInfoResponse fdRecordDataInfoResponse = JSONUtil.toBean(response.getBody(), FDRecordDataInfoResponse.class);
        return fdRecordDataInfoResponse.getFDRecordDataInfo();
    }

    /**
     * TODO 下发人脸数据
     * @param serverId
     */
    public void bindFaceWithMingmouDoorUser(Long serverId, byte[] picture, FDRecordDataInfo faceInfo) {
        int handleId = startRemoteConfig(serverId, HCNetSDK.NET_DVR_FACE_DATA_RECORD, "PUT /ISAPI/Intelligent/FDLib/FDSetUp?format=json");
        String faceInfoStr = JSONUtil.parseObj(faceInfo).toString();
        byte[] faceInfoBytes = faceInfoStr.getBytes();
        HCNetSDK.BYTE_ARRAY faceInfoByteArray = new HCNetSDK.BYTE_ARRAY(faceInfoBytes.length);
        faceInfoByteArray.read();
        faceInfoByteArray.byValue = faceInfoBytes;
        faceInfoByteArray.write();

        HCNetSDK.BYTE_ARRAY pictureByteArray = new HCNetSDK.BYTE_ARRAY(picture.length);
        pictureByteArray.read();
        pictureByteArray.byValue = picture;
        pictureByteArray.write();

        HCNetSDK.NET_DVR_JSON_DATA_CFG inBuffer = new HCNetSDK.NET_DVR_JSON_DATA_CFG();
        inBuffer.read();
        inBuffer.dwSize = inBuffer.size();
        inBuffer.lpJsonData = faceInfoByteArray.getPointer();
        inBuffer.dwJsonDataSize = faceInfoStr.length();
        inBuffer.lpPicData = pictureByteArray.getPointer();
        inBuffer.dwPicDataSize = picture.length;
        inBuffer.write();

        String json = sendWithRecvRemoteConfig(handleId, inBuffer);
        log.info(json);

        stopRemoteConfig(serverId, handleId);
    }

    public IsApiResponseDto isApiRequest(Long serverId, String uri) {
        return isApiRequest(serverId, uri, "");
    }

    /**
     * 发起海康ISAPI请求
     * @param serverId
     * @param uri
     * @param body
     * @return
     */
    public IsApiResponseDto isApiRequest(Long serverId, String uri, String body) {
        autoLogin(serverId);
        int lUserId = SdkServerContextHolder.getUserId(serverId);
        HCNetSDK.NET_DVR_XML_CONFIG_INPUT ipInputParam = new HCNetSDK.NET_DVR_XML_CONFIG_INPUT();
        ipInputParam.read();
        ipInputParam.dwSize = ipInputParam.size();
        HCNetSDK.BYTE_ARRAY ptrUrl = new HCNetSDK.BYTE_ARRAY(uri.length());
        System.arraycopy(uri.getBytes(), 0, ptrUrl.byValue, 0, uri.length());
        ptrUrl.write();
        ipInputParam.lpRequestUrl = ptrUrl.getPointer();
        ipInputParam.dwRequestUrlLen = uri.length();
        if (StrUtil.isBlank(body)) {
            ipInputParam.lpInBuffer = null;
            ipInputParam.dwInBufferSize = 0;
            ipInputParam.write();
        } else {
            HCNetSDK.BYTE_ARRAY ptrInBuffer = new HCNetSDK.BYTE_ARRAY(body.length());
            ptrInBuffer.read();
            ptrInBuffer.byValue = body.getBytes();
            ptrInBuffer.write();

            ipInputParam.lpInBuffer = ptrInBuffer.getPointer();
            ipInputParam.dwInBufferSize = body.length();
            ipInputParam.write();
        }
        HCNetSDK.BYTE_ARRAY ptrStatusByte = new HCNetSDK.BYTE_ARRAY(ISAPI_STATUS_LEN);
        ptrStatusByte.read();

        HCNetSDK.BYTE_ARRAY ptrOutByte = new HCNetSDK.BYTE_ARRAY(ISAPI_DATA_LEN);
        ptrOutByte.read();

        HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT outInputParam = new HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT();
        outInputParam.read();
        outInputParam.dwSize = outInputParam.size();
        outInputParam.lpOutBuffer = ptrOutByte.getPointer();
        outInputParam.dwOutBufferSize = ptrOutByte.size();
        outInputParam.lpStatusBuffer = ptrStatusByte.getPointer();
        outInputParam.dwStatusSize = ptrStatusByte.size();
        outInputParam.write();


        boolean success = hcNetSDK.NET_DVR_STDXMLConfig(lUserId, ipInputParam, outInputParam);
        if (success) {
            outInputParam.read();
            ptrOutByte.read();
            ptrStatusByte.read();
            String strOutXml = new String(ptrOutByte.byValue).trim();
            String strStatus = new String(ptrStatusByte.byValue).trim();
            IsApiResponseDto response = new IsApiResponseDto();
            response.setBody(strOutXml);
            response.setStatus(strStatus);
            return response;
        } else {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("{} 发送ISAPI请求失败，错误码：{}", serverId, errCode);
            throw new FxException("{} 发送ISAPI请求失败，错误码：{}", serverId, errCode);
        }
    }

    public int startRemoteConfig(Long serverId, int dwCommand, String req) {
        return startRemoteConfig(serverId, dwCommand, req, null);
    }
    public int startRemoteConfig(Long serverId, int dwCommand, String req, HCNetSDK.FRemoteConfigCallBack callBack) {
        autoLogin(serverId);
        int lUserId = SdkServerContextHolder.getUserId(serverId);
        HCNetSDK.BYTE_ARRAY lpInBuffer = new HCNetSDK.BYTE_ARRAY(req.length());
        lpInBuffer.read();
        lpInBuffer.byValue = req.getBytes();
        lpInBuffer.write();
        int handleId = hcNetSDK.NET_DVR_StartRemoteConfig(lUserId, dwCommand, lpInBuffer.getPointer(), lpInBuffer.byValue.length, callBack, null);
        if (handleId == -1) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("开始远程配置失败,错误码：{}  {} - {}", errCode, serverId, req);
            throw new FxException("开始远程配置失败,错误码：{}  {} - {}", errCode, serverId, req);
        } else {
            return handleId;
        }
    }

    public void stopRemoteConfig(Long serverId, int handleId) {
        autoLogin(serverId);
        int lUserId = SdkServerContextHolder.getUserId(serverId);
        boolean success = hcNetSDK.NET_DVR_StopRemoteConfig(handleId);
        if (!success) {
            int errCode = hcNetSDK.NET_DVR_GetLastError();
            log.error("结束远程配置失败,错误码：{}  {} - {}", errCode, serverId, handleId);
            throw new FxException("结束远程配置失败,错误码：{}  {} - {}", errCode, serverId, handleId);
        }
    }



    public void autoLogin(Long serverId) {
        boolean logined = SdkServerContextHolder.contains(serverId);
        if (!logined) {
            H2Util.execute(ServerConfigMapper.class, serverConfigMapper -> {
                ServerConfig currentServer = serverConfigMapper.selectOne(Wrappers.<ServerConfig>lambdaQuery().eq(ServerConfig::getId, serverId));
                if (currentServer == null) {
                    throw new FxException("设备不存在或已删除");
                }
                loginV40(currentServer, true);
            });
        }
    }
}
